/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package traffic.light.simulator.town;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import javax.swing.JPanel;
import traffic.light.simulator.town.cars.Ambulance;
import traffic.light.simulator.town.cars.AmbulanceManager;
import traffic.light.simulator.town.cars.Car;
import traffic.light.simulator.town.trafficLight.HybridTrafficLight;
import traffic.light.simulator.town.trafficLight.IPedestrianTrafficLight;
import traffic.light.simulator.town.trafficLight.PedestrianTrafficLight;
import traffic.light.simulator.town.unity.*;
import traffic.light.simulator.util.Position;
import traffic.light.simulator.util.RandomColor;
import traffic.light.simulator.util.Time;

/**
 * Class representing the town
 */
public class Town extends JPanel implements MouseListener {

    //the number of town unity in the town
    private int nbOfTownUnity = 0;
    private ArrayList<Car> arrayOfCar;
    private TownUnity[][] arrayOfTownUnity;
    private Time t;
    private int nbOfCar;
    private Thread ambulanceManagerThread;
    private AmbulanceManager am;
    private boolean editionMode;

    /**
     * Constructor of town
     *
     * @param int : the number of car you want in the town
     * @param String : the link to the file containing the town
     */
    public Town(int nbOfCar) {
        this.nbOfCar = nbOfCar;
        this.arrayOfCar = new ArrayList<Car>();
        this.editionMode = true;
        this.am = new AmbulanceManager(this);
        this.ambulanceManagerThread = new Thread(am);
        addMouseListener(this);
        initFromFile();
    }

    /**
     *
     * @param l
     * @param c
     * @return one TownUnity at line l and column c
     */
    public TownUnity getTownUnity(int l, int c) {
        return this.arrayOfTownUnity[l][c];
    }

    /**
     * Inits the attributes from the file
     */
    private void initFromFile() {
        BufferedReader br;
        try {
            File f = new File(ClassLoader.getSystemResource("traffic/light/simulator/data/Town.txt").toURI());
            br = new BufferedReader(new FileReader(f));
            int sizeOfTownElement = new Integer(br.readLine());
            this.nbOfTownUnity = new Integer(br.readLine());

            TownElement.setSize(sizeOfTownElement);
            this.arrayOfTownUnity = new TownUnity[nbOfTownUnity][nbOfTownUnity];
            this.setSize(new Dimension(TownElement.getSize() * 6 * this.nbOfTownUnity, TownElement.getSize() * 6 * this.nbOfTownUnity));

            for (int l = 0; l < this.nbOfTownUnity; ++l) {
                String[] ligne = br.readLine().split(",");
                for (int c = 0; c < this.nbOfTownUnity; c++) {
                    this.arrayOfTownUnity[l][c] = TownUnityFactory.construct(CrossRoadType.getCrossRoadType(new Integer(ligne[c])), c, l, this.nbOfTownUnity);
                    this.arrayOfTownUnity[l][c].generateCorrespondantUnity();
                }
            }

            Car.SIZE = sizeOfTownElement;
            for (int c = 0; c < this.nbOfCar; ++c) {
                arrayOfCar.add(new Car(RandomColor.getRandomColor(), this));
            }

            br.close();
        } catch (Exception ex) {
            System.out.println("PB reading File" + ex);
        }
    }

    /**
     * Runs all the thread
     *
     * @exception TownNotValidException : this exception is throwed when there
     * is a configuration of town impossible
     */
    public void run() throws TownNotValidException {
        if (isValid()) {
            if (t == null) {
                try {
                    initCarsInTown();
                } catch (Exception e) {
                }

                t = new Time(this);
                t.run();
                ambulanceManagerThread.start();
                for (int l = 0; l < this.arrayOfTownUnity.length; l++) {
                    for (int c = 0; c < this.arrayOfTownUnity[l].length; c++) {
                        this.arrayOfTownUnity[l][c].run();
                    }
                }
                editionMode = false;
            }
        } else {
            throw new TownNotValidException("Le configuration de la ville n'est pas valide");
        }
    }

    /**
     * Updates all the entities of the town
     */
    public void update() {
        ArrayList<Car> carToRemove = new ArrayList<Car>();
        for (Car c : arrayOfCar) {
            c.update();
            if((c.getPosition().getX() == 0 && c.getPosition().getY() == 0)) {
                carToRemove.add(c);
            }
        }
        arrayOfCar.removeAll(carToRemove);
        this.repaint();
    }

    /**
     * Pause all the entities of the town
     */
    public void pause() {
        if (!t.isPaused()) {
            t.pause();
            for (int l = 0; l < this.arrayOfTownUnity.length; l++) {
                for (int c = 0; c < this.arrayOfTownUnity[l].length; c++) {
                    this.arrayOfTownUnity[l][c].pause();
                }
            }
            am.setContinu(false);
        } else {
            t.restart();
            for (int l = 0; l < this.arrayOfTownUnity.length; l++) {
                for (int c = 0; c < this.arrayOfTownUnity[l].length; c++) {
                    this.arrayOfTownUnity[l][c].restart();
                }
            }
            am.setContinu(true);
        }
    }

    /**
     * Inits all the car stored in arrayOfCar
     */
    private void initCarsInTown() {
        for (Car c : arrayOfCar) {
            initCarInTown(c);
        }
    }

    /**
     * Counts the instance of Ambulance stored in arrayOfCar
     */
    public int numberOfAmbulance() {
        int nb = 0;
        for (Car c : arrayOfCar) {
            if (c instanceof Ambulance) {
                nb++;
            }
        }
        return nb;
    }

    /**
     * Search threw all the town unities where it is possible to pop a car
     *
     * @param Car : the car you want to make pop in the town
     */
    public void initCarInTown(Car c) {
        ArrayList<RoadElement> accessibleElements = new ArrayList<RoadElement>();
        if (!arrayOfCar.contains(c)) {
            arrayOfCar.add(c);
        }

        for (int x = 0; x < nbOfTownUnity; ++x) {
            for (int y = 0; y < nbOfTownUnity; ++y) {
                TownUnity tu = arrayOfTownUnity[x][y];
                if (tu.isAccessibleForMovableEntities()) {
                    TownElement te = arrayOfTownUnity[x][y].getAccessibleTownElement();
                    if (te != null && te instanceof RoadElement) {
                        if (((RoadElement) te).isFree()) {
                            accessibleElements.add((RoadElement)te);
                        }
                    }
                }
            }
        }

        if (!accessibleElements.isEmpty()) {
            int i = (int) (Math.random()*100 % accessibleElements.size());
            
            Position p = new Position(accessibleElements.get(i).getAbsX(), accessibleElements.get(i).getAbsY());
            c.setPosition(p);
            if (!(c instanceof Ambulance)) {
                ((RoadElement) accessibleElements.get(i)).setFree(false);
            }
        }
    }

    @Override
    protected void paintComponent(Graphics grphcs) {
        super.paintComponent(grphcs);
        for (int i = 0; i < nbOfTownUnity; ++i) {
            for (int j = 0; j < nbOfTownUnity; ++j) {
                arrayOfTownUnity[i][j].draw(grphcs, editionMode);
            }
        }

        if (t != null) {
            for (Car c : arrayOfCar) {
                c.draw(grphcs);
            }
        }
    }

    /**
     * Use this method when you want to make pop a car in a new town unity. Most
     * of the time you need to use this when the car is out of the town
     */
    public void outOfBounds(Car c) {
        initCarInTown(c);
        //arrayOfCar.remove(c);
    }

    /**
     * Returns true if the town is in a valid configuration of town unities
     */
    public boolean isValid() {
        //TODO fonction isValid
        return true;
    }

    /**
     * Returns the size (length == height) in pixel of the town
     */
    public int getSizeOfTown() {
        return TownElement.getSize() * 6 * this.nbOfTownUnity;
    }

    /**
     * Returns the road element stored at the absolute position p in the town
     * Returns null if there is no road element at this position
     *
     * @param Position : the absolute position where you want to get the road
     * element
     */
    public RoadElement getRoadElementAt(Position p) {
        int x = p.getX() / TownUnity.getSize().width;
        int y = p.getY() / TownUnity.getSize().height;
        TownElement te = null;
        RoadElement road = null;

        if (y < nbOfTownUnity && x < nbOfTownUnity) {
            te = arrayOfTownUnity[y][x].getTownElementAt(p);
        }

        if (te instanceof RoadElement) {
            road = (RoadElement) te;
        }
        return road;
    }

    @Override
    public String toString() {
        return arrayOfCar.toString();
    }

    @Override
    public void mouseClicked(MouseEvent me) {
        if (editionMode) {
            int i = me.getX() / TownUnity.getSize().width;
            int j = me.getY() / TownUnity.getSize().height;
            CrossRoadType currentType = arrayOfTownUnity[j][i].getCrossRoadType();
            int indx = 0;
            for (CrossRoadType crt : CrossRoadType.values()) {
                if (crt.name().equals(currentType.name())) {
                    break;
                }
                indx++;
            }
            CrossRoadType newType = CrossRoadType.getCrossRoadType((indx + 1) % CrossRoadType.values().length);
            arrayOfTownUnity[j][i] = TownUnityFactory.construct(newType, i, j, nbOfTownUnity);
            arrayOfTownUnity[j][i].generateCorrespondantUnity();
            this.repaint();
        } else {
            int i = me.getX() / TownUnity.getSize().width;
            int j = me.getY() / TownUnity.getSize().height;
            TownUnity tu = arrayOfTownUnity[j][i];
            if (tu.getTL() instanceof PedestrianTrafficLight || tu.getTL() instanceof HybridTrafficLight) {
                ((IPedestrianTrafficLight) tu.getTL()).CallFromPedestrian();
            }
        }
    }

    @Override
    public void mousePressed(MouseEvent me) {
    }

    @Override
    public void mouseReleased(MouseEvent me) {
    }

    @Override
    public void mouseEntered(MouseEvent me) {
    }

    @Override
    public void mouseExited(MouseEvent me) {
    }

    public class TownNotValidException extends Exception {

        public TownNotValidException(String message) {
            super(message);
        }
    }
}
